namespace ODataValidator.Rule.Helper
{
    #region Namespaces
    using System;
    using System.Linq;
    using Newtonsoft.Json.Linq;
    using ODataValidator.RuleEngine;
    using ODataValidator.RuleEngine.Common;
    #endregion

    /// <summary>
    /// Utility class of JSON response parser. 
    /// </summary>
    public static class JsonParserHelper
    {
        /// <summary>
        /// Gets JSON response object from service context object.
        /// </summary>
        /// <param name="context">The service context</param>
        /// <returns>JObject of the response in JSON format; null otherwise</returns>
        public static JObject GetResponseObject(ServiceContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            return JsonParserHelper.GetResponseObject(context.ResponsePayload);
        }

        /// <summary>
        /// Gets JSON object of response payload.
        /// </summary>
        /// <param name="payload">The payload literal</param>
        /// <returns>JObject of the response in JSON format; null otherwise</returns>
        public static JObject GetResponseObject(string payload)
        {
            JObject result = null;

            if (payload.TryToJObject(out result))
            {
                // do nothing; already get it in result object
            }

            return result;
        }

        /// <summary>
        /// Gets array of JSON object representing collection of entities.
        /// </summary>
        /// <param name="feed">The JSON objet of response payload for feed type</param>
        /// <returns>JArray object represenign the feed; null if it is not feed in JSON</returns>
        public static JArray GetEntries(JObject feed)
        {
            JArray result = null;

            if (feed != null && feed.Count == 1)
            {
                var d = (JProperty)feed.First;
                if (d.Name.Equals("d", StringComparison.Ordinal))
                {
                    var sub = d.Value;

                    if (sub.Type == JTokenType.Array)
                    {
                        // V1 JSON format
                        result = (JArray)sub;
                    }
                    else if (sub.Type == JTokenType.Object)
                    {
                        // V2 format: looking for name-value pair of "result:{...}"
                        JObject resultObject = (JObject)sub;
                        var resultValues = from p in resultObject.Children<JProperty>()
                                           where p.Name.Equals("results", StringComparison.Ordinal)
                                           && p.Value.Type == JTokenType.Array
                                           select (JArray)p.Value;

                        if (resultValues.Any())
                        {
                            result = resultValues.First();
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the last entry from the collection of entries.
        /// </summary>
        /// <param name="entries">The collection of entries</param>
        /// <returns>JOBject of the last entity </returns>
        public static JObject GetLastEntry(JArray entries)
        {
            JObject lastEntry = null;

            if (entries != null && entries.Count > 0)
            {
                JToken lastItem = entries.Last(o => o.Type == JTokenType.Object);
                lastEntry = (JObject)lastItem;
            }

            return lastEntry;
        }

        /// <summary>
        /// Gets the opaque token value of the entity.
        /// </summary>
        /// <param name="entry">The entity</param>
        /// <returns>The token value if one is found; null otherwise</returns>
        public static string GetTokenOfEntry(JObject entry)
        {
            string token = null;

            string selfTag = JsonHelper.GetPropertyOfElement(entry, "__metadata", "uri");
            if (!string.IsNullOrEmpty(selfTag))
            {
                int length = selfTag.Length;
                if (selfTag[length - 1] == ')')
                {
                    int posLastSeg = selfTag.LastIndexOf('/');
                    int openParenthesis = posLastSeg >= 0 ? selfTag.IndexOf('(', posLastSeg) : selfTag.LastIndexOf('(');
                    token = selfTag.Substring(openParenthesis + 1, length - openParenthesis - 2);
                }
            }

            return token;
        }

        /// <summary>
        /// Validates a Json schema against a payload string
        /// </summary>
        /// <param name="jschema">The JSon schema</param>
        /// <param name="payload">The payload string</param>
        /// <param name="testResult">output parameter of detailed test result</param>
        /// <returns>Returns true when the payload is validated; otherwise false</returns>
        public static bool ValidateJson(string jschema, string payload, out RuleEngine.TestResult testResult)
        {
            JsonSchemaVerifier verifer = new JsonSchemaVerifier(jschema);
            return verifer.Verify(payload, out testResult);
        }

        /// <summary>
        /// Gets the OData version implication from Json payload object 
        /// </summary>
        /// <param name="jo">The Json payload input</param>
        /// <returns>The OData version implication based on the Json object wrapping</returns>
        public static ODataVersion GetPayloadODataVersion(JObject jo)
        {
            ODataVersion result = ODataVersion.UNKNOWN;

            var d = from p in jo.Properties()
                    where p.Name.Equals("d", StringComparison.Ordinal)
                    select p;

            if (d.Any())
            {
                result = ODataVersion.V1;

                var dToken = d.First().Value;
                if (dToken.Type == JTokenType.Object)
                {
                    var dObject = (JObject)dToken;
                    var r = from p in dObject.Properties()
                            where p.Name.Equals("result", StringComparison.Ordinal)
                            select p;
                    if (r.Any())
                    {
                        result = ODataVersion.V2;
                    }
                }
            }

            return result;
        }
    }
}
